/**
 * Copyright (c) 2011 Nokia Corporation.
 */

#include <QtGui>

#ifdef Q_OS_LINUX
#include <QX11Info>
#endif

#ifdef MEEGO_EDITION_HARMATTAN
#include <qmeegographicssystemhelper.h>
#endif

#include "gamewindow.h"

using namespace GE;


/*!
  \class GameWindow
  \brief QtWidget with native OpenGL ES 1.1 support. Replaces QGLWidget when
         real OpenGL ES is required instead of QtOpenGL.
*/


/*!
  Constructor.
*/
GameWindow::GameWindow(QWidget *parent)
    : QWidget(parent),
      m_prevTime(0),
      m_currentTime(0),
      m_frameTime(0.0f),
      m_fps(0.0f),
      m_paused(true),
      m_timerId(0),
      m_hdConnected(false)
{
    setAutoFillBackground(false);
    setAttribute(Qt::WA_OpaquePaintEvent);
    setAttribute(Qt::WA_NoSystemBackground);
    setAttribute(Qt::WA_NativeWindow);
    setAttribute(Qt::WA_PaintOnScreen, true);
    setAttribute(Qt::WA_StyledBackground, false);
    setAttribute(Qt::WA_PaintUnclipped);


#ifdef MEEGO_EDITION_HARMATTAN
    QMeeGoGraphicsSystemHelper::setSwitchPolicy(QMeeGoGraphicsSystemHelper::NoSwitch);
#endif // MEEGO_EDITION_HARMATTAN
}


/*!
  Destructor.
*/
GameWindow::~GameWindow()
{
    destroy();
    if (m_timerId)
        killTimer(m_timerId);
}


/*!
  Initializes OpenGL.
*/
void GameWindow::create()
{
    createEGL();
    onCreate();
    onInitEGL();

    m_currentTime = getTickCount();
    m_prevTime = m_currentTime;
    m_fps = 0.0f;
    m_frameTime = 0.0f;

    resume();
}


/*!
  Called when the game window is to be destroyed.
*/
void GameWindow::destroy()
{
    onFreeEGL();

    eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroySurface(eglDisplay, eglSurface);

    onDestroy();
}




/*!
  Returns the tick count in milliseconds.
*/
unsigned int GameWindow::getTickCount() const
{
    QTime now = QTime::currentTime();
    return (now.hour() * 3600 + now.minute() * 60 + now.second()) * 1000
            + now.msec();
}


/*!
  Pauses the execution.
*/
void GameWindow::pause()
{
    if (m_paused)
        return;

    m_paused = true;
    killTimer(m_timerId);
    m_timerId = 0;
    onPause();
}


/*!
  Resumes from the pause mode.
*/
void GameWindow::resume()
{
    if (!m_paused)
        return;

#ifdef Q_OS_SYMBIAN
    // Wait a little while to be able to see the new profile correctly.
    User::After( 500000 );
#endif // Q_OS_SYMBIAN

    m_paused = false;
    onResume();
    m_timerId = startTimer(0);
}

float GameWindow::getFrameTime() const
{
    return m_frameTime;
}

float GameWindow::getFPS() const
{
    return m_fps;
}

void GameWindow::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
}


/**
  Return native window handle
*/
EGLNativeWindowType GameWindow::getWindow()
{
    EGLNativeWindowType pwd;

#ifdef Q_OS_SYMBIAN
        pwd = (EGLNativeWindowType)(winId()->DrawableWindow());
#else // !Q_OS_SYMBIAN
    pwd = (EGLNativeWindowType)winId();
#endif // Q_OS_SYMBIAN

    return pwd;
}



/*!
  From QObject.

  Filters \a event sent or posted by \a object. If the event is
  ActivationChanged event, the pause mode is toggled. Otherwise the event is
  propagated.
*/
bool GameWindow::eventFilter(QObject *object, QEvent *event)
{
    if (event->type() == QEvent::ActivationChange) {
        if (isActiveWindow())
            resume();
        else
            pause();

        return false;
    }
    // Let the event propagate for standard event processing.
    return QObject::eventFilter(object, event);
}

int GameWindow::width()
{
    return QWidget::width();
}

int GameWindow::height()
{
    return QWidget::height();
}

/*!
  From QWidget.

  Handles the resize events.
*/
void GameWindow::resizeEvent(QResizeEvent *event)
{
    int w, h;
    if (event->size().width() == event->oldSize().width() &&
            event->size().height() == event->oldSize().height()) {
        // The size has not changed.
        return;
    }

    w = event->size().width();
    h = event->size().height();
    glViewport(0, 0, w, h);
    onSizeChanged(w, h);

    QWidget::resizeEvent(event);
}


/*!
  From QObject.

  Handles the timer events.
*/
void GameWindow::timerEvent(QTimerEvent *event)
{
    Q_UNUSED(event);
    render();
}

/*!
  Called after OpenGL ES 2.0 is created to let the application to allocate
  its resources.

  To be implemented in the derived class.
*/
void GameWindow::onCreate()
{
}

void GameWindow::onInitEGL()
{

}

void GameWindow::onFreeEGL()
{

}


/*!
  Called when everything is about to be destroyed and the application is
  shutting down.

  To be implemented in the derived class.
*/
void GameWindow::onDestroy()
{

}


/*!
  Called when application needs to (re)render its screen.

  To be implemented in the derived class.
*/
void GameWindow::onRender()
{

}


/*!
  Called when the framework is going into pause mode.

  To be implemented in the derived class.
*/
void GameWindow::onPause()
{

}


/*!
  Called when the framework is resuming from the pause mode.

  To be implemented in the derived class.
*/
void GameWindow::onResume()
{

}


/*!
  Called once before each frame, \a frameDelta attribute is set as the time
  between current frame and the previous one.

  To be implemented in the derived class.
*/
void GameWindow::onUpdate(const float frameDelta)
{
    Q_UNUSED(frameDelta);
}


/*!
  Called when the size of the screen has been changed. The application could
  update its projection, viewport and other size specific stuff here.

  To be implemented in the derived class.
*/
void GameWindow::onSizeChanged(int width, int height)
{
    Q_UNUSED(width);
    Q_UNUSED(height);
}


/*!
  Create and initialize objects required for OpenGL rendering
*/
void GameWindow::createEGL()
{
    // EGL variables
    eglDisplay	= 0;
    eglConfig	= 0;
    eglSurface	= 0;
    eglContext	= 0;

#ifdef Q_OS_LINUX
    eglDisplay =
        eglGetDisplay((EGLNativeDisplayType)x11Info().display());
#endif // Q_OS_LINUX

#ifdef Q_OS_WIN32
    HWND hwnd = this->winId();
    HDC dc = GetWindowDC(hwnd);
    eglDisplay = eglGetDisplay((EGLNativeDisplayType)dc);
#endif // Q_OS_WIN32

#ifdef Q_OS_SYMBIAN
    eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
#endif // Q_OS_SYMBIAN


    EGLint majorVersion;
    EGLint minorVersion;

    if (!eglInitialize(eglDisplay, &majorVersion, &minorVersion)) {
         cleanupAndExit(eglDisplay);
    }

    EGLint pi32ConfigAttribs[13];
    pi32ConfigAttribs[0] = EGL_SURFACE_TYPE;
    pi32ConfigAttribs[1] = EGL_WINDOW_BIT;
    pi32ConfigAttribs[2] = EGL_RENDERABLE_TYPE;
    pi32ConfigAttribs[3] = EGL_OPENGL_ES_BIT;

    pi32ConfigAttribs[4] = EGL_NONE;
    /*
    pi32ConfigAttribs[5] = 8;
    pi32ConfigAttribs[6] = EGL_NONE;
    */

    EGLint pi32ContextAttribs[3];
    pi32ContextAttribs[0] = EGL_CONTEXT_CLIENT_VERSION;
    pi32ContextAttribs[1] = 1;
    pi32ContextAttribs[2] = EGL_NONE;

    EGLint configs;

    if (!eglChooseConfig(eglDisplay, pi32ConfigAttribs, &eglConfig, 1, &configs)
            || (configs != 1))  {
        qDebug() << "Failed in eglChooseConfig.";
        cleanupAndExit(eglDisplay);
    }
    qDebug() << "1";


    eglSurface = eglCreateWindowSurface(eglDisplay, eglConfig, getWindow(), NULL);

    if (!testEGLError("eglCreateWindowSurface")) {
        cleanupAndExit(eglDisplay);
    }

    eglContext = eglCreateContext(eglDisplay, eglConfig, NULL,
                                  pi32ContextAttribs);

    if (!testEGLError("eglCreateContext")) {
        qDebug() << "failed to create context";
        cleanupAndExit(eglDisplay);
    }

    eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);

    if (!testEGLError("eglMakeCurrent")) {
        qDebug() << "Error in eglMakeCurrent";
        cleanupAndExit(eglDisplay);
    }
}


/*!
  Main run - method for the QtGameEnabler, processes a single frame and
  calls necesseary functions of the application using QtGE: update and
  onRender.
*/
void GameWindow::render()
{
    m_prevTime = m_currentTime;
    m_currentTime = getTickCount();
    m_frameTime = (float)(m_currentTime - m_prevTime) * 0.001f;

    if (m_frameTime != 0.0f)
        m_fps = 1.0f / m_frameTime;
    else
        m_fps = 100000.0f;


    onUpdate(m_frameTime);
    onRender();

    if (!eglSwapBuffers(eglDisplay, eglSurface)) {
        // eglSwapBuffers() failed!
        GLint errVal = eglGetError();

        /*
        EXPLANATION:

        http://library.forum.nokia.com/index.jsp?topic=/Nokia_Symbian3_Developers_Library/GUID-894AB487-C127-532D-852B-37CB0DEA1440.html

        On the Symbian platform, EGL handles the window resize in the next
        call to eglSwapBuffers(), which resizes the surface to match the new
        window size. If the preserve buffer option is in use, this function
        also copies across all the pixels from the old surface that overlap
        the new surface, although the exact details depend on the
        implementation.

        If the surface resize fails, eglSwapBuffers() returns EGL_FALSE and an
        EGL_BAD_ALLOC error is raised. This may mean that the implementation
        does not support the resizing of a surface or that there is not enough
        memory available (on a platform with GPU, this would be GPU rather
        than system memory).

        Applications must always monitor whether eglSwapBuffers() fails after
        a window resize. When it does fail, the application should do the
        following:
          - Call eglDestroySurface() to destroy the current EGL window surface.
          - Call eglCreateWindowSurface() to recreate the EGL window surface.

        This may cause a noticeable flicker and so should be done only when
        necessary.
        */

        if (errVal == EGL_BAD_ALLOC || errVal == EGL_BAD_SURFACE) {


            eglDestroySurface(eglDisplay, eglSurface);

            eglSurface = eglCreateWindowSurface(eglDisplay,
                                                eglConfig,
                                                getWindow(),
                                                NULL);
            eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
        }
        else {
            cleanupAndExit(eglDisplay);
        }
    }
}


/*!
  Check for EGL errors. \a pszLocation should contain the last called EGL
  function and is used for DEBUG_INFO print. The method returns true in case of no
  errors, false otherwise.
*/
bool GameWindow::testEGLError(const char *pszLocation)
{
    EGLint err = eglGetError();

    if (err != EGL_SUCCESS) {
        return false;
    }

    return true;
}


/*!
  Cleans up the content of \a eglDisplay and exits.
*/
void GameWindow::cleanupAndExit(EGLDisplay eglDisplay)
{
    eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroySurface(eglDisplay, eglSurface);
    eglTerminate(eglDisplay);
    exit(0);
}

void GameWindow::reinitEGL()
{
    onFreeEGL();

    eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroySurface(eglDisplay, eglSurface);

    createEGL();
    onInitEGL();
}



// !! GLHelpers !! Not actually part of the official GE, but helps a lot the actual programming.



/*!
  Load an image with QImage, convert it to GL's format, create OpenGL texture and
  Return it's handle
*/
GLuint GameWindow::glhelpLoadTexture( QString imageFile )
{
    QImage image = QImage( imageFile );
    if (image.width()<1 || image.height()<1) return 0;      // Failed to load the texture
    GLuint texture;
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    GLuint* pTexData = new GLuint[image.width() * image.height()];
    GLuint* sdata = (GLuint*)image.bits();
    GLuint* tdata = pTexData;

    for (int y=0; y<image.height(); y++)
        for (int x=0; x<image.width(); x++) {
            *tdata = ((*sdata&255) << 16) | (((*sdata>>8)&255) << 8)
                    | (((*sdata>>16)&255) << 0) | (((*sdata>>24)&255) << 24);

            sdata++;
            tdata++;
    };

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width(), image.height(), 0,
                 GL_RGBA, GL_UNSIGNED_BYTE, pTexData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    delete [] pTexData;
    return texture;
}
